#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "GridView.h"
#include "Class.h"
#include "Math.h"
#include "Clipper.h"
#include "ET_stdio.h"

//---- CellAttribute -----------------------------------------------------------

CellAttributes::CellAttributes()
{
	ink= gInkBlack;
	font= gSysFont;
	align= (VObjAlign) (eAdjHLeft|eAdjVTop);
}

CellAttributes::CellAttributes(Ink *i, Font *f, VObjAlign a)
{
	ink= i;
	font= f;
	align= a;
}

CellAttributes::CellAttributes(const CellAttributes *ca)
{
	ink= ca->ink;
	font= ca->font;
	align= ca->align;
}

//---- GridSelector ------------------------------------------------------------

GridSelector::GridSelector(GridView *gv, bool r, int c, int cl)
{
	gridview= gv;
	range= r;
	code= c;
	clicks= cl;
}

void GridSelector::TrackFeedback(Point, Point, bool)
{
}

Command *GridSelector::TrackMouse(TrackPhase atp, Point, Point, Point np)
{
	Point cell;
	if (!gridview->PosToCell(np, &cell)) {
		if (atp == eTrackPress) {
			anchor= cell;
			sel= gridview->GetSelection();
		}
		Rectangle r;
		if (range)
			r= NormRect(anchor, cell);
		else
			r= Rectangle(cell, gPoint1);
		switch (code) {
		case 0:
			gridview->SetSelection(r);
			break;
		case 1:
			gridview->SetSelection(Union(sel, r));
			break;
		case 2:
			gridview->SetSelection(Difference(sel, r));
			break;
		}
	} else
		gridview->ClearSelection();
	if (atp == eTrackPress) {
		anchor= cell;
		sel= gridview->GetSelection();
	}

	if (atp == eTrackRelease) {
		gridview->DoSelect(clicks);
		return gNoChanges;
	}
	return this;
}

//---- GridView ----------------------------------------------------------------

NewMetaImpl(GridView,View, (T(selection), T(border), TB(cache), TB(modified)));

GridView::GridView(EvtHandler *eh, bool c) : View(eh, gRect0)
{
	cache= c;
	modified= TRUE;
}

GridView::~GridView()
{
	SafeDelete(grid);
}

Point GridView::GetColsRows()
{
	return gPoint8;
}

int GridView::GetGap(Direction, int)
{
	return 3;
}
		
Metric GridView::CellSize(const Point&)
{
	return Metric(1);
}

int GridView::GetColWidth(int col)
{
	register int y, w= 0, rows= GetRows();
	for (y= 0; y < rows; y++)
		w= Math::Max(w, CellSize(Point(col, y)).Width());
	return w;
}

int GridView::GetRowHeight(int row)
{
	register int x, cols= GetCols(), h= 0;
	for (x= 0; x < cols; x++)
		h= Math::Max(h, CellSize(Point(x, row)).Height());
	return h;
}

int GridView::GetColOrigin(int)
{
	return 0;
}

int GridView::GetRowOrigin(int)
{
	return 0;
}

int GridView::GetRowBase(int row)
{
	register int x, cols= GetCols(), b= 0;
	for (x= 0; x < cols; x++)
		b= Math::Max(b, CellSize(Point(x, row)).Base());
	return b;
}

CellAttributes *GridView::GetCellAttributes(const Point&)
{
	static CellAttributes ca;
	return &ca;
}

void GridView::DrawCell(const Point&, const Rectangle&, const Rectangle&, bool)
{
}

void GridView::DrawGridRect(Direction dir, int, const Rectangle &r)
{
	if (r.IsNotEmpty()) {
		if (dir == eHor)
			GrLine(r.W(), r.E());
		else
			GrLine(r.N(), r.S());
	}
}

void GridView::Draw(Rectangle r)
{
	Point p, p1, p2;
	PosToCell(r.NW(), &p1);
	PosToCell(r.SE(), &p2);
	Rectangle r1, r2, cellrect(GetGapCellRect(NormRect(p1, p2)));
	
	r1.origin.x= cellrect.origin.x;
	r1.extent.x= cellrect.extent.x;

	r2.origin.y= cellrect.origin.y;
	r2.extent.y= cellrect.extent.y;

	for (p.y= p1.y; p.y <= p2.y; p.y++) {
		for (p.x= p1.x; p.x <= p2.x; p.x++) {
			Rectangle cr(GetCellOrigin(p)+GetOrigin(), GetCellExtent(p));
			DrawCell(p, cr, Inter(cr, r), selection.ContainsPoint(p));
			if (p.y == p1.y) {
				r2.extent.x= GetColGap(p.x);
				r2.origin.x= ColOrigin(p.x+1)-r2.extent.x;
				DrawGridRect(eVert, p.x, r2);
			}
		}
		r1.extent.y= GetRowGap(p.y);
		r1.origin.y= RowOrigin(p.y+1)-r1.extent.y;
		DrawGridRect(eHor, p.y, r1);
	}

	if (border.x > 0)
		GrStrokeRect(contentRect.Inset(border));
}

Metric GridView::GetMinSize()
{
	return Metric(ColOrigin(GetCols())+border.x,
		      RowOrigin(GetRows())+border.y,
				RowBase(0)+border.y);
}

void GridView::ConstrainScroll(Point *p)
{
	//Point cell;
	//if (!PosToCell(*p, &cell))
	//        p->y= RowOrigin(cell.y);
}

bool GridView::PosToCell(const Point &pos, Point *cell)
{
	register int base, last, x, y, s, e;
	register bool out= FALSE;
	Point p(pos-GetOrigin()-border);
	int cols= GetCols(), rows= GetRows();
	bool found;
	
	if (p.x < 0) {
		out= TRUE;
		x= 0;
	} else {
		found= FALSE;
		base= 0;
		last= cols-1;
		while (last >= base) {
			x= (base + last) / 2;
			s= ColOrigin(x);
			e= s+ColWidth(x)+GetColGap(x);
			if (p.x >= s && p.x < e) {
				found= TRUE;
				break;
			}
			if (p.x < s)
				last= x - 1;
			else
				base= x + 1;
		}
		if (!found) {
			out= TRUE;
			x= cols-1;
		}
	}

	if (p.y < 0) {
		out= TRUE;
		y= 0;
	} else {
		found= FALSE;
		base= 0;
		last= rows-1;
		while (last >= base) {
			y= (base + last) / 2;
			s= RowOrigin(y);
			e= s+RowHeight(y)+GetRowGap(y);
			if (p.y >= s && p.y < e) {
				found= TRUE;
				break;
			}
			if (p.y < s)
				last= y - 1;
			else
				base= y + 1;
		}
		
		if (!found) {
			out= TRUE;
			y= rows-1;
		}
	}

	if (cell) {
		cell->x= x;
		cell->y= y;
	}

	return out;
}

Rectangle GridView::GetCellRect(const Point &cell)
{
	if (cell.x < 0 || cell.x >= GetCols() || cell.y < 0 || cell.y >= GetRows())
		return gRect0;
	return Rectangle(GetCellOrigin(cell)+GetOrigin(), GetCellExtent(cell));
}

Rectangle GridView::GetCellRect(const Rectangle &cells)
{
	if (cells.IsEmpty())
		return gRect0;
	return NormRect(GetCellOrigin(cells.NW())+GetOrigin(),
		GetCellOrigin(cells.SE())+GetCellExtent(cells.SE())+GetOrigin());
}

Rectangle GridView::GetGapCellRect(const Point &cell)
{
	if (cell.x < 0 || cell.x >= GetCols() || cell.y < 0 || cell.y >= GetRows())
		return gRect0;
	return NormRect(GetCellOrigin(cell)+GetOrigin(),
				GetCellOrigin(cell+gPoint1)-gPoint1+GetOrigin());
}

Rectangle GridView::GetGapCellRect(const Rectangle &cells)
{
	if (cells.IsEmpty())
		return gRect0;
	return NormRect(GetCellOrigin(cells.NW())+GetOrigin(),
				GetCellOrigin(cells.origin+cells.extent)-gPoint1+GetOrigin());
}

bool GridView::CheckCache()
{
	if (cache) {
		if (modified) {
			modified= FALSE;

			register int w;
			Point p;
			int cols= GetCols(), rows= GetRows();

			SafeDelete(grid);
			grid= new gridinfo[Math::Max(cols, rows)+1];
		
			grid[0].x= border.x;
			grid[0].y= border.y;

			for (p.x= 0; p.x < cols; p.x++) {
				w= 0;
				for (p.y= 0; p.y < rows; p.y++) {
					Metric m(CellSize(p));
					w= Math::Max(w, m.Width());
					grid[p.y+1].y= Math::Max(grid[p.y+1].y, m.Height());
					grid[p.y].bs= Math::Max(grid[p.y].bs, m.Base());
				}
				grid[p.x+1].x= w;
			}
		
			for (p.x= 1; p.x <= cols; p.x++)
				grid[p.x].x+= grid[p.x-1].x + GetColGap(p.x-1);
			for (p.y= 1; p.y <= rows; p.y++)
				grid[p.y].y+= grid[p.y-1].y + GetRowGap(p.y-1);
		
			SetExtent(GetMinSize().extent);

			return TRUE;
		}
		return TRUE;
	}
	return FALSE;
}

void GridView::SetSelection(const Rectangle &newsel)
{
	SetSelection(Region(newsel));
}

void GridView::SetSelection(const Region &newrgn)
{
	Rectangle all(gPoint0, GetColsRows());
	Region newsel(Intersection(newrgn, all));
	if (selection != newsel) {
		Rectangle r;
		Region reg(SymDifference(selection, newsel));
		RegionRectIter next(reg);
		while (next(r))
			InvalidateRect(GetGapCellRect(r));
		selection= newsel;
		Control(GetId(), cPartSelectionChanged, &selection);
	}
}

void GridView::DoSelect(int clicks)
{
	if (selection.IsNotEmpty()) {
		int partcode= clicks >= 2 ? cPartCollDoubleSelect : cPartCollSelect;
		int y= selection.BBox().origin.y;
		Control(GetId(), partcode, (void*)y);
	}
}

Command *GridView::DoLeftButtonDownCommand(Point lp, Token t, int clicks)
{
	int code= 0;

	if (t.Flags & eFlgShiftKey) {
		Point cell;
		PosToCell(lp, &cell);
		if (selection.ContainsPoint(cell))
			code= 2;
		else
			code= 1;
	}
	return new GridSelector(this, FALSE, code, clicks);
}

